﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Security.Principal;
using System.ServiceModel;
using System.Web;
using Metro.Kashikoi.Common;
using Metro.Kashikoi.Common.ExceptionHandling;
using Metro.Kashikoi.ServiceContract.Item;
using Metro.Kashikoi.ServiceContract.Location;
using Metro.Kashikoi.ServiceContract.Storage;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace Metro.Kashikoi.ServiceContract
{
    /// <summary>
    /// The service bus route the service request by providing the service channel.
    /// </summary>
    public class ServiceBus
    {
        private static IDictionary<string, Tuple<string, object[]>> channels;
        private static IUnityContainer container;
        private static string serviceBusSetting;
        static ServiceBus()
        {
            channels = new Dictionary<string, Tuple<string, object[]>>();
            container = new UnityContainer()
                .LoadConfiguration(Constants.UnityContainerServiceBus);
            serviceBusSetting = ConfigurationManager.AppSettings[Constants.AppSettingServiceBus];
        }

        /// <summary>
        /// Registers the specified channel.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <param name="channelType">Type of the channel.</param>
        /// <param name="args">The args.</param>
        public static void Register<TService>(string channelType, params object[] args) where TService : class
        {
            var type = Type.GetType(channelType);
            var serviceType = typeof(TService);
            string serviceName = serviceType.Name;
            if (type.IsSubclassOf(serviceType))
            {
                throw new AppException(string.Format(
                    "Invalidate type {0} of service {1}", channelType, serviceName));
            }
            Tuple<string, object[]> t = Tuple.Create(channelType, args);
            if (channels.ContainsKey(serviceName))
            {
                channels[serviceName] = t;
            }
            else
            {
                channels.Add(serviceName, t);
            }
        }

        /// <summary>
        /// Gets the service.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <returns>The service adapter.</returns>
        public static TService GetService<TService>() where TService : class
        {
            TService serviceClient = null;
            string name = typeof(TService).Name;
            if (channels.ContainsKey(name))
            {
                Tuple<string, object[]> t = channels[name];
                serviceClient = ReflectionUtil.CreateInstance<TService>(t.Item1, t.Item2);
            }
            else
            {
                foreach (var reg in container.Registrations)
                {
                    if (reg.Name == serviceBusSetting && typeof(TService) == reg.RegisteredType)
                    {
                        serviceClient = container.Resolve(reg.RegisteredType, reg.Name) as TService;
                        break;
                    }
                }
            }
            return SetClientCredentials(serviceClient);
        }

        // Replaced by the Unity.
        ///// <summary>
        ///// Initialize without WCF.
        ///// </summary>
        //public static void InitWcf()
        //{
        //    Register<ILocationService>(
        //        "Metro.Kashikoi.ServiceContract.Location.LocationServiceClient, Metro.Kashikoi.ServiceContract");
        //    Register<IItemService>(
        //        "Metro.Kashikoi.ServiceContract.Item.ItemServiceClient, Metro.Kashikoi.ServiceContract");
        //    Register<ICategoryService>(
        //        "Metro.Kashikoi.ServiceContract.Item.CategoryServiceClient, Metro.Kashikoi.ServiceContract");
        //    Register<IVendorService>(
        //        "Metro.Kashikoi.ServiceContract.Item.VendorServiceClient, Metro.Kashikoi.ServiceContract");
        //    Register<IStorageService>(
        //        "Metro.Kashikoi.ServiceContract.Storage.StorageServiceClient, Metro.Kashikoi.ServiceContract");
        //}

        /// <summary>
        /// Initialize without WCF.
        /// </summary>
        public static void InitNoneWcf()
        {
            Register<ILocationService>(
                "Metro.Kashikoi.Service.Location.Service.LocationService, Metro.Kashikoi.Service.Location");
            Register<IItemService>(
                "Metro.Kashikoi.Service.Item.Service.ItemService, Metro.Kashikoi.Service.Item");
            Register<ICategoryService>(
                "Metro.Kashikoi.Service.Item.Service.ItemService, Metro.Kashikoi.Service.Item");
            Register<IVendorService>(
                "Metro.Kashikoi.Service.Item.Service.ItemService, Metro.Kashikoi.Service.Item");
            Register<IStorageService>(
                "Metro.Kashikoi.Service.Storage.Service.StorageService, Metro.Kashikoi.Service.Storage");
        }

        private static IService SetClientCredentials<IService>(IService client) where IService : class
        {
            if (client != null && client is ClientBase<IService>)
            {
                if (HttpContext.Current != null &&
                   HttpContext.Current.Session != null)
                {
                    var identity = HttpContext.Current.Session[Constants.SessionIdentity] as IIdentity;
                    var password = HttpContext.Current.Session[Constants.SessionPassword] as string;
                    if (identity != null && !string.IsNullOrEmpty(password))
                    {
                        (client as ClientBase<IService>).ClientCredentials.UserName.UserName = identity.Name;
                        (client as ClientBase<IService>).ClientCredentials.UserName.Password = password;
                    }
                }
            }
            return client;
        }
    }
}
